package org.cainiao.servlet.concurrent.util.lock;

import lombok.RequiredArgsConstructor;
import org.redisson.api.RedissonClient;
import org.redisson.client.codec.StringCodec;
import org.springframework.stereotype.Component;

import java.time.Duration;
import java.util.Collections;

import static org.redisson.api.RScript.Mode;
import static org.redisson.api.RScript.ReturnType;

/**
 * <br />
 * <p>
 * Author: Cai Niao(wdhlzd@163.com)<br />
 */
@Component
@RequiredArgsConstructor
public class RedisDistributedLock {

    private final RedissonClient redissonClient;

    /**
     * 尝试获取锁
     *
     * @param lockKey    锁的key
     * @param value      锁的值（唯一标识）
     * @param expireTime 过期时间（毫秒）
     * @return 是否获取成功
     */
    public boolean tryLock(String lockKey, String value, long expireTime) {
        return redissonClient.getBucket(lockKey, StringCodec.INSTANCE)
            .setIfAbsent(value, Duration.ofMillis(expireTime));
    }

    /**
     * 轮询等待获取锁
     *
     * @param lockKey    锁的key
     * @param value      唯一标识
     * @param expireTime 锁的过期时间（毫秒）
     * @param waitTime   最大等待时间（毫秒）
     * @param interval   轮询间隔（毫秒）
     * @return 是否获取成功
     * @throws InterruptedException 中断异常
     */
    public boolean lock(String lockKey, String value,
                        long expireTime, long waitTime, long interval)
        throws InterruptedException {
        long start = System.currentTimeMillis();
        while (true) {
            if (tryLock(lockKey, value, expireTime)) {
                return true;
            }
            if (System.currentTimeMillis() - start >= waitTime) {
                return false; // 超时
            }
            Thread.sleep(interval); // 等待
        }
    }

    /**
     * 释放锁
     *
     * @param lockKey 锁的key
     * @param value   唯一标识
     */
    public void unlock(String lockKey, String value) {
        String script =
            "if redis.call('get', KEYS[1]) == ARGV[1] then " +
                "    return redis.call('del', KEYS[1]) " +
                "else " +
                "    return 0 " +
                "end";
        redissonClient.getScript(StringCodec.INSTANCE)
            .eval(Mode.READ_WRITE, script, ReturnType.INTEGER, Collections.singletonList(lockKey), value);
    }
}
